package main

import (
	"errors"
	"fmt"
	"math"
	"time"
)

type GPS struct {
	Longitude float64
	Latitude  float64
	SpeedKmph float64
	TrueTrack float64
	Timestamp time.Time
}

type ShipVoyage struct {
	GPS               GPS                 `json:"gps,omitempty" bson:"gps,omitempty"`
	ShipID            ShipIDType          `json:"shipID,omitempty" bson:"shipID,omitempty"`
	PrevWharf         Wharf               `json:"prevWharf,omitempty" bson:"prevWharf,omitempty"`
	NextWharf         Wharf               `json:"nextWharf,omitempty" bson:"nextWharf,omitempty"`
	IsArrived         bool                `json:"isArrived,omitempty" bson:"isArrived,omitempty"`
	ChannelCandidates []ShipChannelIDType `json:"channelCandidates,omitempty" bson:"channelCandidates,omitempty"`
	nearbyWharf       Wharf               `json:"nearbyWharf,omitempty" bson:"nearbyWharf,omitempty"`
	prevDistance      float64             `json:"prevDistance,omitempty" bson:"prevDistance,omitempty"`
}

type MovingDirection int

const (
	MovingUnknown = iota
	MovingApproch
	MovingLeave
)

func (v ShipVoyage) isChannelKnown() bool {
	return len(v.ChannelCandidates) == 1
}

func (v ShipVoyage) Update(context *Context, data VoyageUpdatePayload) (ShipVoyage, error) {
	if v.isChannelKnown() {
		return v.update(data)
	} else {
		return v.determinChannel(context, data)
	}
}

func (v ShipVoyage) update(data VoyageUpdatePayload) (ShipVoyage, error) {
	return ShipVoyage{}, errors.New("unimplemented")
}

func (v ShipVoyage) determinChannel(context *Context, data VoyageUpdatePayload) (ShipVoyage, error) {
	wharf, err := v.findNearbyWharf(context, data)
	if err != nil {
		return v, err
	}
	if wharf != nil {
		return v.determinChannelInFence(context, data, *wharf)
	} else {
		return v.determinChannelOutFence(data, *wharf)
	}
}

func (v ShipVoyage) findNearbyWharf(context *Context, data VoyageUpdatePayload) (*Wharf, error) {
	db, err := context.GetWharfRepo()
	if err != nil {
		return nil, err
	}
	wharfs, err := db.GetAll()
	if err != nil {
		return nil, err
	}
	for _, w := range wharfs {
		if w.InFence(data.Longitude, data.Latitude) {
			return &w, nil
		}
	}
	return nil, nil
}

func (v ShipVoyage) isStopped(data VoyageUpdatePayload) bool {
	if math.Abs(data.SpeedKmph) < 1 {
		return true
	} else {
		return false
	}
}

func (v ShipVoyage) determinChannelInFence(context *Context, data VoyageUpdatePayload, wharf Wharf) (ShipVoyage, error) {
	stopped := v.isStopped(data)
	if stopped {
		v.NextWharf = wharf
		v.IsArrived = true
		v.ChannelCandidates, _ = v.findChannelCandidatesByArrive(context, data, wharf)
		return v, nil
	} else {
		return v.determinChannelInFenceMoving(data, wharf)
	}
}

func (v ShipVoyage) findChannelCandidatesByArrive(context *Context, data VoyageUpdatePayload, wharf Wharf) ([]ShipChannelIDType, error) {
	db, err := context.GetShipVoyageScheduleDB()
	if err != nil {
		return []ShipChannelIDType{}, err
	}
	sched, err := db.GetByShipID(v.ShipID)
	if err != nil {
		return []ShipChannelIDType{}, err
	}
	if sched == nil {
		return []ShipChannelIDType{}, fmt.Errorf("ship %v has no schedule", v.ShipID)
	}
	candidates := []ShipChannelIDType{}
	for idx, w := range sched.Wharfs {
		if w == wharf.ID {
			index := idx - 1
			if index < 0 {
				index = len(sched.Channels) - 1
			}
			candidates = append(candidates, sched.Channels[index])
		}
	}
	return candidates, nil
}

func (v ShipVoyage) determinChannelInFenceMoving(data VoyageUpdatePayload, wharf Wharf) (ShipVoyage, error) {
	move := v.checkMoving(data, wharf)
	switch move {
	case MovingApproch:
		{
			v.IsArrived = true
			v.PrevWharf = wharf
			// v.NextWharf = v.findNextWharf()
			break
		}
	case MovingLeave:
		{
			v.IsArrived = false
			v.NextWharf = wharf
			break
		}
	default:
		{
			break
		}
	}
	return v, nil
}

func (v ShipVoyage) checkMoving(data VoyageUpdatePayload, wharf Wharf) MovingDirection {
	d := wharf.Distance(data.Longitude, data.Latitude)
	if d > v.prevDistance {
		return MovingLeave
	} else if d < v.prevDistance {
		return MovingApproch
	} else {
		return MovingUnknown
	}
}

// func (v ShipVoyage) findNextWharf() Wharf {

// }

func (v ShipVoyage) determinChannelOutFence(data VoyageUpdatePayload, wharf Wharf) (ShipVoyage, error) {
	return ShipVoyage{}, errors.New("unimplemented")
}
